#include "spl.includes.h"
#include "splib.h"

using namespace SPLib;
using namespace SPLib::Geom;
using namespace SPLib::Controls;
using namespace Gdiplus;

#pragma region Macros
#define TOOLBAR_SPACE               5
#define BUTTON_ROUND                3

#define BUTTONMENU_MARGIN           24.0f
#define CONTENT_SPACE               5.0f
#define ARROW_WIDTH                 14.0f
#define ARROW_HEIGHT                6.0f

#define UPDOWN_WIDTH                20

#define ITEM_INCREMENT              10

#define FONT_FAMILY                 _T("Microsoft Sans Serif")
#define FONT_SIZE                   10.0f
#define FONT_STYLE                  Gdip::FontStyleBold

#define Toolbar_IsStatic(x)         ((x) & TOOLITEMMASK_STATIC)
#define Toolbar_IsMenu(x)           ((x) & TOOLITEMMASK_MENU)
#define Toolbar_IsDown(i, p)        ((mItems[i]->state & TOOLITEM_CHECKED) || \
  (itemPressed == i && \
  (itemPart == p || mItems[i]->type != \
  ToolItemButtonMenu)))
#define Toolbar_IsOvered(i, p)      (i == itemOvered && itemPart == p)
#define Toolbar_HasState(i, s)      (mItems[i]->state & (s))
#define Toolbar_HasStates(i, s)     ((mItems[i]->state & (s)) == (s))

#define TOOLITEMFLAG_LEFTCONNECT    0x01
#define TOOLITEMFLAG_RIGHTCONNECT   0x02
#define TOOLITEMFLAG_CONNECTED      (TOOLITEMFLAG_LEFTCONNECT | \
  TOOLITEMFLAG_RIGHTCONNECT)
#define TOOLITEMFLAG_OVERED         0x04
#define TOOLITEMFLAG_PRESSED        0x08
#define TOOLITEMFLAG_SELECTED       (TOOLITEMFLAG_OVERED | \
  TOOLITEMFLAG_PRESSED)
#pragma endregion

ToolBar::ToolBar() :
mItems(5),
  itemOvered(-1),
  itemPressed(-1),
  backColor(0xFFFFFF)
{
  font = CreateFont(
    16,
    0,
    0,
    0,
    FW_DONTCARE,
    FALSE,
    FALSE,
    FALSE,
    DEFAULT_CHARSET,
    OUT_OUTLINE_PRECIS,
    CLIP_DEFAULT_PRECIS,
    CLEARTYPE_QUALITY,
    VARIABLE_PITCH,
    _T("Arial")
    );
}

VOID    ToolBar::onCreate(Event::WndCreateEvent& iEvent)
{
}

VOID    ToolBar::onDestroy()
{
  for (UINT i = 0; i < mItems.getLength(); ++i)
    freeItem(mItems[i]);
}

VOID    ToolBar::onKeyDown(Event::KeyEvent& iEvent)
{
  switch (iEvent.vkCode)
  {
  case VK_LEFT:
    selectPrevItem(itemOvered);
    break;
  case VK_RIGHT:
    selectNextItem(itemOvered);
    break;
  case VK_SPACE:
  case VK_RETURN:
    doAction();
    break;
  case VK_DOWN:
    if (Toolbar_IsMenu(mItems[itemOvered]->type))
      doAction();
    break;
  }
}

VOID    ToolBar::onMouseMove(Event::MouseEvent& iEvent)
{
  hitTestItem(iEvent.point);
}

VOID    ToolBar::onMouseLeave()
{
  if (GetFocus() == *this) return;

  hitTestItem(Geom::Point::fromCursor(*this));

  /*if (itemOvered != -1)  invalidateItem(itemOvered);
  if (itemPressed != -1) invalidateItem(itemPressed);

  itemOvered = itemPressed = -1;*/
}

VOID    ToolBar::onMouseDown(Event::MouseEvent& iEvent)
{
  if (itemOvered != -1)
  {
    if (mItems[itemOvered]->bounds.Contains((REAL)iEvent.x, (REAL)iEvent.y))
    {
      invalidateItem(itemPressed = itemOvered);
      update();

      doAction();
      hitTestItem(Geom::Point::fromCursor(*this));
    }
  }
}

VOID    ToolBar::onMouseUp(Event::MouseEvent& iEvent)
{
  if (itemPressed != -1)
  {
    invalidateItem(itemPressed);
    itemPressed = -1;
  }
}

VOID    ToolBar::onSetFocus(Event::FocusEvent& iEvent)
{
  if (itemOvered == -1)
    selectNextItem(-1);
}

VOID    ToolBar::onKillFocus(Event::FocusEvent& iEvent)
{
  if (itemOvered != -1)
  {
    invalidateItem(itemOvered);
    itemOvered = -1;
  }
}

VOID    ToolBar::onEraseBackground(Event::EraseBackgroundEvent& iEvent)
{
  *iEvent.handled = TRUE;
}

VOID    ToolBar::onSize(Event::SizeEvent& iEvent)
{
  Control::onSize(iEvent);
  positionItems(0);
}

VOID    ToolBar::onControlPaint(Controls::ControlPaintEvent& iEvent)
{
  iEvent.graphics.Clear(backColor | 0xFF000000);

  for (UINT i = 0; i < mItems.getLength(); ++i)
  {
    if (mItems[i]->type == ToolItemBreak)
      continue;

    BOOL visible =
      mItems[i]->bounds.X + mItems[i]->bounds.Width <= iEvent.bounds.Width;

    if (visible)
    {
      drawItemBackground(iEvent.graphics, i);
      drawItem(iEvent.graphics, i);
    }

    if (mItems[i]->window)
    {
      if (IsWindowVisible(mItems[i]->window) != visible)
      {
        ShowWindow(mItems[i]->window, visible ? SW_SHOW : SW_HIDE);

        if (mItems[i]->type == ToolItemNumeric)
        {
          ShowWindow((HWND)SendMessage(mItems[i]->window,
            UDM_GETBUDDY, 0, 0), visible ? SW_SHOW : SW_HIDE);
        }
      }
    }
  }
}

VOID    ToolBar::getItemShape(
  Gdip::GraphicsPath  &path,
  const Gdip::RectF   &bounds,
  INT                 flags
  )
{
  // arc bounds
  Gdip::RectF arc(bounds.X, bounds.Y, BUTTON_ROUND * 2, BUTTON_ROUND * 2);

  if (flags & TOOLITEMFLAG_LEFTCONNECT) {
    path.AddLine(arc.X, arc.Y + bounds.Height, arc.X, arc.Y);
  } else {
    arc.Y += bounds.Height - arc.Height;

    // bottom left
    path.AddArc(arc, 90, 90);
    arc.Y -= bounds.Height - arc.Height;

    // top left
    path.AddArc(arc, 180, 90);
  }

  arc.X += bounds.Width;

  if (flags & TOOLITEMFLAG_RIGHTCONNECT) {
    path.AddLine(arc.X, arc.Y, arc.X, arc.Y + bounds.Height);
  } else {
    arc.X -= arc.Width;

    // top right
    path.AddArc(arc, 270, 90);
    arc.Y += bounds.Height - arc.Height;

    // bottom right
    path.AddArc(arc, 0, 90);
    arc.X -= bounds.Width - arc.Width;
  }

  path.CloseFigure();
}

VOID    ToolBar::drawItemPart(
  Gdip::Graphics &g,
  Gdip::RectF &bounds,
  INT index,
  ItemPart part
  )
{
  // button background colors
  COLORREF bkColor1 = 0, bkColor2 = 0;
  COLORREF colorBorder = 0x3F000000;
  // button shape, inset shape
  GraphicsPath path, inpath;
  // button border pen
  Pen border(Control::getTheme().borderLight);
  // button flags
  INT flags = mItems[index]->flags;

  // get background colors
  if (!Toolbar_HasState(index, TOOLITEM_DISABLED))
  {
    if (Toolbar_IsDown(index, part))
    {
      bkColor1    = Control::getTheme().frameDark;
      bkColor2    = Control::getTheme().frameSurface;
      colorBorder = Control::getTheme().borderDark;
    }
    else if (Toolbar_IsOvered(index, part))
    {
      bkColor1    = Control::getTheme().frameLight;
      bkColor2    = Control::getTheme().frameHover;
      colorBorder = Control::getTheme().borderDark;
    }
  }

  if (mItems[index]->type == ToolItemButtonMenu)
  {
    switch (part)
    {
    case ItemPartMenu:
      flags |= TOOLITEMFLAG_LEFTCONNECT;
      break;
    case ItemPartContent:
      flags |= TOOLITEMFLAG_RIGHTCONNECT;
      break;
    }
  }

  // button background brush
  LinearGradientBrush grad(bounds, bkColor1, bkColor2, 90);

  // construct button paths
  getItemShape(path, bounds, flags);
  getItemShape(inpath,
    RectF(bounds.X + 1, bounds.Y + 1, bounds.Width - 2, bounds.Height - 2),
    flags);

  // paint background
  g.FillPath(&grad, &path);

  // paint inner border
  if (Toolbar_HasState(index, TOOLITEM_DISABLED) ||
    !Toolbar_IsDown(index, part))
  {
    g.DrawPath(&border, &inpath);
  }

  // paint border
  border.SetColor(colorBorder);
  g.DrawPath(&border, &path);
}

VOID    ToolBar::drawItemBackground(Gdip::Graphics &g, INT index)
{
  // button bounds
  Gdip::RectF bounds = mItems[index]->bounds;

  // remove exceeding pixels
  --bounds.Width;
  --bounds.Height;

  // paint button menu
  switch (mItems[index]->type)
  {
  case ToolItemButton:
    drawItemPart(g, bounds, index, ItemPartContent);
    break;
  case ToolItemButtonMenu:
    bounds.Width -= BUTTONMENU_MARGIN;
    drawItemPart(g, bounds, index, ItemPartContent);

    bounds.X = bounds.GetRight();
    bounds.Width = BUTTONMENU_MARGIN;
    drawItemPart(g, bounds, index, ItemPartMenu);
    break;
  case ToolItemMenu:
    drawItemPart(g, bounds, index, ItemPartMenu);
    break;
  case ToolItemSeparator:
    g.DrawLine(&Pen(0xFF666666),
      bounds.X,
      bounds.Y + 5,
      bounds.X,
      bounds.GetBottom() - 1 - 5);
    g.DrawLine(&Pen(0xFFFFFFFF),
      bounds.X + 1,
      bounds.Y + 5,
      bounds.X + 1,
      bounds.GetBottom() - 1 - 5);
    break;
  }
}

VOID    ToolBar::drawItem(Gdip::Graphics &g, INT index)
{
  // button text format
  StringFormat format;
  setStringFormat(format);
  // item foreground color
  COLORREF foreColor;

  // text bounds
  RectF textRect = mItems[index]->bounds;

  if (mItems[index]->type == ToolItemLabel ||
    mItems[index]->state & TOOLITEM_DISABLED)
  {
    foreColor = Control::getTheme().textDisabled;
  } else {
    foreColor = Toolbar_IsDown(index, ItemPartContent) ?
      Control::getTheme().textLight :
    Control::getTheme().textDark;
  }

  // draw button menu specific glyphs
  if (mItems[index]->type == ToolItemButtonMenu ||
    mItems[index]->type == ToolItemMenu)
  {
    textRect.Width -= BUTTONMENU_MARGIN;

    Gdip::PointF ptArrow(
      (BUTTONMENU_MARGIN - ARROW_WIDTH) / 2 + textRect.GetRight() - 1,
      textRect.Y + (textRect.Height - ARROW_HEIGHT) / 2);

    // arrow points
    PointF arrow[] = {
      PointF(ptArrow.X,                   ptArrow.Y),
      PointF(ptArrow.X + ARROW_WIDTH / 2, ptArrow.Y + ARROW_HEIGHT),
      PointF(ptArrow.X + ARROW_WIDTH,     ptArrow.Y)
    };

    g.FillPolygon(&SolidBrush(Toolbar_IsDown(index, ItemPartMenu) ?
      Control::getTheme().textLight :
    Control::getTheme().textDark),
      arrow, 3);
  }

  if (mItems[index]->type != ToolItemLabel) {
    textRect.X += CONTENT_SPACE;
    textRect.Width -= CONTENT_SPACE * 2;
  }

  // paint button text
  g.DrawString(
    mItems[index]->szText, -1,
    &Font(FONT_FAMILY, FONT_SIZE, FONT_STYLE),
    textRect, &format, &SolidBrush(foreColor));
}

VOID    ToolBar::measureItems(INT startIndex)
{
  Geom::Rect rect = getClient();
  Gdip::SizeF size(0, TOOLBAR_HEIGHT);

  for (UINT i = startIndex; i < mItems.getLength(); ++i) {
    size.Width = (REAL)mItems[i]->size;

    switch (mItems[i]->type) {
    case ToolItemSeparator:
      if (!size.Width) size.Width = 2.0f;
      break;
    case ToolItemBreak:
      continue;
    default:
      if (!size.Width) {
        StringFormat format;
        setStringFormat(format);

        Graphics(*this).MeasureString(
          mItems[i]->szText, -1,
          &Font(FONT_FAMILY, FONT_SIZE, FONT_STYLE),
          SizeF(200.0f, size.Height),
          &format, &size);

        size.Width  = ceil(size.Width);
        size.Width  = max(10.0f, size.Width);
        size.Height = (REAL)TOOLBAR_HEIGHT;

        if (mItems[i]->type != ToolItemLabel)
          size.Width += CONTENT_SPACE * 2;

        if (mItems[i]->type & TOOLITEMMASK_MENU)
          size.Width += BUTTONMENU_MARGIN;
      }
      break;
    }

    mItems[i]->bounds.Width = size.Width;
    mItems[i]->bounds.Height = size.Height;
  }

  positionItems(startIndex);

  invalidate(&Geom::Rect(
    (LONG)mItems[startIndex]->bounds.X,
    (LONG)mItems[startIndex]->bounds.Y,
    rect.right, rect.bottom), FALSE);
}

VOID    ToolBar::positionItems(INT startIndex)
{
  Geom::Rect rect = getClient();
  Gdip::PointF pos;

  if (startIndex == 0) {
    pos = Gdip::PointF(0, 0);
  } else {
    pos.X = mItems[startIndex - 1]->bounds.X;
    pos.Y = mItems[startIndex - 1]->bounds.Y;

    positionItem(startIndex - 1, pos);
  }

  INT lastBreak = 0;

  for (UINT i = startIndex; i < mItems.getLength(); ++i) {
    if (mItems[i]->type == ToolItemBreak) {
      lastBreak = i;
      continue;
    }

    if (pos.X + mItems[i]->bounds.Width > rect.getWidth() &&
      lastBreak > 0)
    {
      for (UINT j = lastBreak + 1; j <= i; ++j) {
        if (j == lastBreak + 1) {
          pos.X = 0;
          pos.Y += TOOLBAR_HEIGHT + TOOLBAR_SPACE;
        }

        positionItem(j, pos);
      }

      lastBreak = 0;
    } else {
      positionItem(i, pos);
    }
  }

  setSize(
    getRect().getWidth(),
    mItems.getLength() > 0 ?
    (LONG)mItems[mItems.getLength() - 1]->bounds.GetBottom() :
    0);
}

VOID    ToolBar::positionItem(UINT i, Gdip::PointF &pos)
{
  if (mItems[i]->window) {
    switch (mItems[i]->type) {
    case ToolItemNumeric:
      SetWindowPos(
        (HWND)SendMessage(mItems[i]->window, UDM_GETBUDDY, 0, 0),
        NULL,
        (LONG)pos.X,
        (LONG)pos.Y,
        (LONG)(mItems[i]->bounds.Width - UPDOWN_WIDTH),
        (LONG)mItems[i]->bounds.Height,
        SWP_NOREPOSITION);

      SetWindowPos(
        mItems[i]->window,
        NULL,
        (LONG)(pos.X + mItems[i]->bounds.Width - UPDOWN_WIDTH),
        (LONG)pos.Y,
        (LONG)UPDOWN_WIDTH,
        (LONG)mItems[i]->bounds.Height,
        SWP_NOREPOSITION);
      break;
    case ToolItemEdit:
      SetWindowPos(
        mItems[i]->window,
        NULL,
        (LONG)pos.X,
        (LONG)pos.Y,
        (LONG)mItems[i]->bounds.Width,
        (LONG)mItems[i]->bounds.Height,
        SWP_NOREPOSITION);
      break;
    }
  }

  mItems[i]->flags &= ~TOOLITEMFLAG_CONNECTED;
  mItems[i]->bounds.X = pos.X;
  mItems[i]->bounds.Y = pos.Y;

  pos.X += mItems[i]->bounds.Width;

  if (!Toolbar_IsStatic(mItems[i]->type)) {
    if (i + 1 < mItems.getLength()) {
      if (!Toolbar_IsStatic(mItems[i + 1]->type))
        mItems[i]->flags |= TOOLITEMFLAG_RIGHTCONNECT;
    }

    if (i > 0) {
      if (!Toolbar_IsStatic(mItems[i - 1]->type))
        mItems[i]->flags |= TOOLITEMFLAG_LEFTCONNECT;
    }
  }

  if (mItems[i]->flags & TOOLITEMFLAG_RIGHTCONNECT)
    pos.X -= 1;
  else
    pos.X += TOOLBAR_SPACE;
}

VOID    ToolBar::invalidateItem(INT index)
{
  invalidate(&Geom::Rect(
    (LONG)(mItems[index]->bounds.X),
    (LONG)(mItems[index]->bounds.Y),
    (LONG)(mItems[index]->bounds.X + mItems[index]->bounds.Width),
    (LONG)(mItems[index]->bounds.Y + mItems[index]->bounds.Height)),
    FALSE);
}

VOID    ToolBar::freeItem(ToolBarItem* item)
{
  switch (item->type) {
  case ToolItemButton:
  case ToolItemButtonMenu:
    DestroyMenu((HMENU)item->typeData);
  case ToolItemNumeric:
    DestroyWindow((HWND)SendMessage(
      (HWND)item->window, UDM_GETBUDDY, 0, 0));
  case ToolItemEdit:
    DestroyWindow(item->window);
    break;
  }

  delete item;
}

VOID    ToolBar::setItemText(PCTSTR szText, INT nID)
{
  for (UINT i = 0; i < mItems.getLength(); ++i) {
    if (mItems[i]->nID == nID) {
      _tcscpy_s(mItems[i]->szText, TOOLBAR_MAXTEXT + 1, szText);
      measureItems(i);
      break;
    }
  }
}

VOID    ToolBar::addItem(
  PTCHAR szText,
  PTCHAR szInfo,
  ToolItemType type)
{
  ToolItem item;

  _tcscpy_s(item.szText, TOOLBAR_MAXTEXT + 1, szText);

  item.type = type;
  item.state = TOOLITEM_NORMAL;

  addItems(&item, 1);
}

VOID    ToolBar::addItems(ToolItem *pInsertItems, UINT count)
{
  for (UINT i = 0; i < count; ++i)
  {
    mItems.add(new ToolBarItem);

    ToolBarItem* &item = mItems[mItems.getLength() - 1];

    memset(item, 0, sizeof(ToolBarItem));
    memcpy(item, &pInsertItems[i], sizeof(ToolItem));

    HWND window;

    switch (item->type)
    {
    case ToolItemEdit:
      window = CreateWindowEx(
        WS_EX_LEFT | WS_EX_CLIENTEDGE | WS_EX_CONTEXTHELP,
        WC_EDIT, _T(""), WS_CHILD | WS_TABSTOP |
        ES_LEFT | ES_WANTRETURN | ES_AUTOHSCROLL,
        0, 0, 0, 0, *this, NULL, getInstance(), NULL);

      SetWindowFont(window, font, FALSE);
      SendMessage(window, EM_SETLIMITTEXT, TOOLBAR_MAXTEXT, 0);

      item->window = window;
      break;
    case ToolItemNumeric:
      {
        UDACCEL numStep[] =
        {
          { 0, 1                                            },
          { 1, ((ItemDataNumeric*)item->typeData)->inc      },
          { 2, ((ItemDataNumeric*)item->typeData)->inc * 2  }
        };

        window = CreateWindowEx(
          WS_EX_LEFT | WS_EX_CLIENTEDGE | WS_EX_CONTEXTHELP,
          WC_EDIT, _T(""), WS_CHILD | WS_TABSTOP |
          ES_NUMBER | ES_LEFT | ES_AUTOHSCROLL | ES_WANTRETURN,
          0, 0, 0, 0, *this, NULL, getInstance(), NULL);
        item->window = CreateWindowEx(
          WS_EX_LEFT | WS_EX_LTRREADING, UPDOWN_CLASS, _T(""),
          WS_CHILD | UDS_ARROWKEYS | UDS_HOTTRACK | UDS_SETBUDDYINT,
          0, 0, 0, 0, *this, NULL, getInstance(), NULL);

        SetWindowFont(window, font, FALSE);
        //SetWindowLongPtr(window, GWLP_ID, mItems.getLength() - 1);
        SetWindowLongPtr(item->window, GWLP_ID, mItems.getLength() - 1);

        SendMessage(item->window, UDM_SETBUDDY, (WPARAM)window, 0);
        SendMessage(item->window, UDM_SETRANGE32,
          ((ItemDataNumeric*)item->typeData)->min,
          ((ItemDataNumeric*)item->typeData)->max);
        SendMessage(item->window, UDM_SETPOS32, 0,
          ((ItemDataNumeric*)item->typeData)->pos);
        SendMessage(item->window, UDM_SETACCEL,
          sizeof(numStep) / sizeof(numStep[0]), (LPARAM)numStep);
      }
      break;
    }
  }

  measureItems(mItems.getLength() - count);
}

BOOL    ToolBar::removeItems(UINT iIndex, UINT iCount)
{
  BOOL wResult = FALSE;

  if (iIndex >= 0 && iIndex + iCount <= mItems.getLength() && iCount > 0)
  {
    for (UINT i = 0; i < iCount; ++i)
      freeItem(mItems[iIndex + i]);

    mItems.remove(iIndex, iCount);
    wResult = TRUE;
  }

  setSize(
    getRect().getWidth(),
    mItems.getLength() > 0 ?
    (LONG)mItems[mItems.getLength() - 1]->bounds.GetBottom() :
    0);

  return wResult;
}

BOOL    ToolBar::getItems(ToolItem *pItem, UINT iIndex, UINT iCount)
{
  if (iIndex >= 0 && iIndex + iCount < mItems.getLength() && iCount > 0) {
    memcpy(pItem, mItems.getBuffer()[iIndex], sizeof(ToolItem) * iCount);
    return TRUE;
  }

  return FALSE;
}

const ToolItem* ToolBar::getItemFromID(INT iID)
{
  for (UINT i = 0; i < mItems.getLength(); ++i)
  {
    if (mItems[i]->nID = iID)
      return mItems[i];
  }

  return NULL;
}

VOID    ToolBar::getItemBounds(INT iID, Geom::Bounds& bounds)
{
  for (UINT i = 0; i < mItems.getLength(); ++i)
  {
    if (mItems[i]->nID == iID)
    {
      bounds = Bounds(
        (LONG)mItems[i]->bounds.X,
        (LONG)mItems[i]->bounds.Y,
        (LONG)mItems[i]->bounds.Width,
        (LONG)mItems[i]->bounds.Height);
      return;
    }
  }

  bounds.setEmpty();
}

VOID    ToolBar::setItemChecked(INT iID, BOOL checked)
{
  for (UINT i = 0; i < mItems.getLength(); ++i)
  {
    if (mItems[i]->nID == iID)
    {
      if (checked)
        mItems[i]->state |= TOOLITEM_CHECKED;
      else
        mItems[i]->state &= ~TOOLITEM_CHECKED;

      invalidateItem(i);
      break;
    }
  }
}

BOOL    ToolBar::getItemChecked(INT iID)
{
  for (UINT i = 0; i < mItems.getLength(); ++i)
  {
    if (mItems[i]->nID = iID)
      return mItems[i]->state & TOOLITEM_CHECKED;
  }

  return FALSE;
}

VOID    ToolBar::setStringFormat(Gdip::StringFormat &format)
{
  format.SetAlignment(
    Gdip::StringAlignmentCenter);
  format.SetLineAlignment(
    Gdip::StringAlignmentCenter);
  format.SetTrimming(
    Gdip::StringTrimmingCharacter);
  format.SetFormatFlags(
    Gdip::StringFormatFlagsLineLimit |
    Gdip::StringFormatFlagsNoWrap |
    Gdip::StringFormatFlagsNoClip);
}

VOID    ToolBar::doAction()
{
  if (Toolbar_HasState(itemOvered, TOOLITEM_DISABLED)) return;

  ToolBarNotify data;
  INT index = itemOvered;

  data.code       = NM_LDOWN;
  data.itemID     = mItems[index]->nID;

  if (itemPart == ItemPartMenu && mItems[index]->typeData)
  {
    Geom::Point pt(
      (LONG)mItems[index]->bounds.X,
      (LONG)mItems[index]->bounds.Y);

    pt.setScreen(*this);

    TPMPARAMS menuParams;
    memset(&menuParams, 0, sizeof(menuParams));

    menuParams.cbSize = sizeof(menuParams);
    menuParams.rcExclude = Geom::Rect(
      pt.x, pt.y,
      pt.x + (LONG)mItems[index]->bounds.Width,
      pt.y + (LONG)mItems[index]->bounds.Height);

    data.itemPart   = ItemPartMenuItem;
    data.value      = TrackPopupMenuEx(
      (HMENU)mItems[index]->typeData,
      TPM_NONOTIFY | TPM_RETURNCMD,
      pt.x, pt.y + (LONG)mItems[index]->bounds.Height,
      *this, &menuParams);

    if (data.value)
    {
      if (mItems[index]->state & TOOLITEM_AUTORENAME)
      {
        TCHAR szText[TOOLBAR_MAXTEXT + 1];

        Menu_GetItemText(
          (HMENU)mItems[index]->typeData,
          data.value, szText);

        _tcscpy_s(mItems[index]->szText, TOOLBAR_MAXTEXT + 1, szText);
        measureItems(index);
      }
    } else {
      return;
    }
  } else {
    data.itemPart = itemPart;
    data.value = 0;
  }

  sendNotify(data);
}

VOID    ToolBar::hitTestItem(POINT& pt)
{
  INT newOvered = -1;
  ItemPart newPart = (ItemPart)-1;
  Geom::Rect client = Geom::Rect::fromClient(*this);

  for (UINT i = 0; i < mItems.getLength(); ++i)
  {
    if (mItems[i]->bounds.Contains((FLOAT)pt.x, (FLOAT)pt.y) &&
      mItems[i]->bounds.GetRight() <= client.right &&
      !Toolbar_IsStatic(mItems[i]->type))
    {
      newOvered = i;

      switch (mItems[i]->type)
      {
      case ToolItemMenu:
        newPart = ItemPartMenu;
        break;
      case ToolItemButtonMenu:
        newPart = (pt.x > mItems[i]->bounds.GetRight() - BUTTONMENU_MARGIN) ?
          ItemPartMenu : ItemPartContent;
        break;
      default:
        newPart = ItemPartContent;
        break;
      }
    }
  }

  if (GetFocus() == *this && newOvered == -1) return;

  if (itemOvered != newOvered || itemPart != newPart)
  {
    if (itemOvered != -1 && itemPressed != itemOvered)
      invalidateItem(itemOvered);

    if (newOvered != -1 && itemPressed != newOvered)
      invalidateItem(newOvered);

    itemPart = newPart;
    itemOvered = newOvered;

    if (itemPressed != itemOvered && itemPressed != -1)
    {
      invalidateItem(itemPressed);
      itemPressed = -1;
    }
  }
}

VOID    ToolBar::setItemState(INT state, INT iID)
{
  for (UINT i = 0; i < mItems.getLength(); ++i)
  {
    if (mItems[i]->nID == iID)
    {
      mItems[i]->state = state;
      invalidateItem(i);

      switch (mItems[i]->type)
      {
      case ToolItemNumeric:
        EnableWindow(
          (HWND)SendMessage(mItems[i]->window, UDM_GETBUDDY, 0, 0),
          !(state & TOOLITEM_DISABLED));
      case ToolItemEdit:
        EnableWindow(mItems[i]->window, !(state & TOOLITEM_DISABLED));
        break;
      }
      break;
    }
  }
}

INT     ToolBar::getItemState(INT iID)
{
  for (UINT i = 0; i < mItems.getLength(); ++i)
  {
    if (mItems[i]->nID == iID)
      return mItems[i]->state;
  }

  return -1;
}

VOID ToolBar::onNotify(Event::NotifyEvent& iEvent)
{
  if (iEvent.data->idFrom >= 0 && iEvent.data->idFrom < (UINT)mItems.getLength())
  {
    switch (mItems[(INT)iEvent.data->idFrom]->type)
    {
    case ToolItemNumeric:
      switch (iEvent.data->code)
      {
      case UDN_DELTAPOS:
        ToolBarNotify data;

        data.code       = NM_LDOWN;
        data.itemID     = mItems[(INT)iEvent.data->idFrom]->nID;
        data.itemPart   = ItemPartEdit;
        data.value      = ((NMUPDOWN*)iEvent.data)->iPos +
          ((NMUPDOWN*)iEvent.data)->iDelta;

        sendNotify(data);
        break;
      }
      break;
    }
  }
}

INT     ToolBar::selectPrevItem(INT iIndex)
{
  if (itemOvered != -1)
    invalidateItem(itemOvered);

  itemOvered = iIndex;

  do
  {
    if (itemOvered < 0)
    {
      itemOvered = mItems.getLength() - 1;
    }
    else
    {
      if (mItems[itemOvered]->type == ToolItemButtonMenu &&
        itemPart == ItemPartMenu)
      {
        itemPart = ItemPartContent;
      }
      else
      {
        if (--itemOvered < 0) itemOvered = mItems.getLength() - 1;

        itemPart = Toolbar_IsMenu(mItems[itemOvered]->type) ?
          ItemPartMenu : ItemPartContent;
      }
    }
  }
  while (Toolbar_IsStatic(mItems[itemOvered]->type));

  invalidateItem(itemOvered);
  return itemOvered;
}

INT     ToolBar::selectNextItem(INT iIndex)
{
  if (itemOvered != -1)
    invalidateItem(itemOvered);

  itemOvered = iIndex;

  do
  {
    if (itemOvered < 0)
    {
      itemOvered  = 0;
      itemPart    = ItemPartContent;
    }
    else
    {
      if (mItems[itemOvered]->type == ToolItemButtonMenu &&
        itemPart == ItemPartContent)
      {
        itemPart = ItemPartMenu;
      }
      else
      {
        if ((UINT)++itemOvered >= mItems.getLength()) itemOvered = 0;

        itemPart = mItems[itemOvered]->type == ToolItemMenu ?
          ItemPartMenu : ItemPartContent;
      }
    }
  }
  while (Toolbar_IsStatic(mItems[itemOvered]->type));

  invalidateItem(itemOvered);
  return itemOvered;
}
